package com.sailing.xjpb.orm.utils;

import com.google.common.base.CaseFormat;
import com.sailing.xjpb.common.DataConverter;
import com.sailing.xjpb.common.SailingReflectionUtils;
import com.sailing.xjpb.common.exception.BusinessException;
import com.sailing.xjpb.common.exception.ConversionException;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.HibernateException;
import org.hibernate.property.access.internal.PropertyAccessStrategyBasicImpl;
import org.hibernate.property.access.internal.PropertyAccessStrategyChainedImpl;
import org.hibernate.property.access.internal.PropertyAccessStrategyFieldImpl;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.property.access.spi.Setter;
import org.hibernate.transform.AliasedTupleSubsetResultTransformer;
import org.slf4j.helpers.FormattingTuple;
import org.slf4j.helpers.MessageFormatter;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Map;

/**
 * Result transformer that allows to transform a result to a user specified
 * class which will be populated via setter methods or fields matching the alias
 * names.
 * <p/>
 *
 * @author max
 */
@Slf4j
public class AliasToBeanResultTransformer extends AliasedTupleSubsetResultTransformer {

    // IMPL NOTE : due to the delayed population of setters (setters cached
    // for performance), we really cannot properly define equality for
    // this transformer
    /**
     *
     */
    private static final long serialVersionUID = 1L;
    private final Class<?> resultClass;
    private boolean isInitialized;
    private String[] aliases;
    private Setter[] setters;
    private Getter[] getters;
    // 将数据库类型转换为javabean字段匹配的类型，否则会报错
    private final DataConverter dataConverter;

    public AliasToBeanResultTransformer(Class<?> resultClass) {
        if (resultClass == null) {
            throw new IllegalArgumentException("resultClass cannot be null");
        }
        dataConverter = new DataConverter();
        isInitialized = false;
        this.resultClass = resultClass;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
        return false;
    }

    @Override
    public Object transformTuple(Object[] tuple, String[] aliases) {
        Object result;

        try {
            if (!isInitialized) {
                initialize(aliases);
            } else {
                check(aliases);
            }

            result = resultClass.newInstance();
            boolean isMapObject = result instanceof Map;
            for (int i = 0; i < aliases.length; i++) {
                if (setters[i] != null) {
                    setters[i].set(result, convert(getters[i], tuple[i]), null);
                } else if (isMapObject) {
                    ((Map) result).put(toCamelCase(aliases[i]), tuple[i]);
                }
            }
        } catch (InstantiationException e) {
            throw new HibernateException("Could not instantiate resultclass: " + resultClass.getName());
        } catch (IllegalAccessException | ConversionException e) {
            throw new HibernateException(e);
        }

        return result;
    }

    private void initialize(String[] aliases) {
        PropertyAccessStrategyChainedImpl propertyAccessStrategy = new PropertyAccessStrategyChainedImpl(
                PropertyAccessStrategyBasicImpl.INSTANCE, PropertyAccessStrategyFieldImpl.INSTANCE,
                SailingPropertyAccessStrategyImpl.INSTANCE);
        this.aliases = new String[aliases.length];
        setters = new Setter[aliases.length];
        getters = new Getter[aliases.length];
        for (int i = 0; i < aliases.length; i++) {
            String alias = aliases[i];
            if (alias != null) {
                this.aliases[i] = alias;
                try {
                    String fieldAlias = toCamelCase(alias);
                    initGetterAndSetter(propertyAccessStrategy, i, fieldAlias);
                } catch (Exception e) {
                    if (log.isDebugEnabled()) {
                        log.warn("initGetterAndSetter出错！将使用类中的字段名称（忽略大小写）再次匹配！");
                    }
                    try {
                        Field field = SailingReflectionUtils.getDeclaredField(resultClass,
                                f -> f.getName().equalsIgnoreCase(alias));
                        if (field != null) {
                            initGetterAndSetter(propertyAccessStrategy, i, field.getName());
                        } else if (log.isDebugEnabled()) {
                            log.warn("未找到：{}类型对应的数据库映射字段:{}", resultClass, alias);
                        }
                    } catch (Exception ex) {
                        if (log.isDebugEnabled()) {
                            log.warn("initGetterAndSetter出错！错误信息：{}", e.getMessage());
                        }
                    }
                }
            }
        }
        isInitialized = true;
    }

    private void initGetterAndSetter(PropertyAccessStrategyChainedImpl propertyAccessStrategy, int i, String alias) {
        try {
            PropertyAccess propertyAccess = propertyAccessStrategy.buildPropertyAccess(resultClass, alias);
            setters[i] = propertyAccess.getSetter();
            getters[i] = propertyAccess.getGetter();
        } catch (Exception e) {
            FormattingTuple errorMsg = MessageFormatter.format("字段{}不是类{}的一个field", alias, resultClass);
            throw BusinessException.businessError(errorMsg.getMessage());
        }
    }

    private void check(String[] aliases) {
        if (!Arrays.equals(aliases, this.aliases)) {
            throw new IllegalStateException("aliases are different from what is cached; aliases="
                    + Arrays.asList(aliases) + " cached=" + Arrays.asList(this.aliases));
        }
    }

    private Object convert(Getter getter, Object tuple) {

        if (tuple == null) {
            return null;
        }

        if (getter == null) {
            return tuple;
        }

        Class<?> destFieldClass = getter.getReturnType();
        if (tuple.getClass() == destFieldClass) {
            return tuple;
        } else {
            return dataConverter.convert(tuple, destFieldClass);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        AliasToBeanResultTransformer that = (AliasToBeanResultTransformer) o;

        if (!resultClass.equals(that.resultClass)) {
            return false;
        }
        return Arrays.equals(aliases, that.aliases);
    }

    @Override
    public int hashCode() {
        int result = resultClass.hashCode();
        result = 31 * result + (aliases != null ? Arrays.hashCode(aliases) : 0);
        return result;
    }

    /**
     * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空，则返回空字符串。</br>
     * 例如：HELLO_WORLD->helloWorld
     *
     * @param name 转换前的下划线大写方式命名的字符串
     * @return 转换后的驼峰式命名的字符串
     */
    public static String toCamelCase(String name) {
        return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name);
    }
}
